Making your own REVEN Axion plugin step by step


Jun 02, 2015
by Mathieu
Categories: REVEN -
Tags: Reven Axion - Plugin - REVEN -




In this article we will shed light on REVEN Axion’s customisation possibilities by describing step by step how to create a simple plugin.

Percent plugin screenshot

Percent plugin in action on push edi.

We will walk you through:

  • The specification of our plugin
  • The basics of plugin API for REVEN Axion
  • The implementation of the plugin’s core mechanics

You can download the complete percent plugin file here. Percent plugin screenshot

Other plugin examples are available in REVEN documentation

Plugin specification

  1. When triggered from a selected instruction, jump to the matching one
  2. Select matching instructions as follows:

    • push: next instruction reading pushed value (may not be a pop).
    • pop: previous instruction writting popped value (may not be a push).
    • sysenter/sysexit
    • call/ret
    • int/iret
    • leave/push ebp

Note that the matching instruction may not exist or may not have been recorded in current trace.

The plugin codename will be percent (referring to vi’s ‘%’ for “goto match”)

Getting called by REVEN Axion

REVEN Axion plugin basics:

  • REVEN Axion will call plugin’s REVEN Axion_callback() function if it exists when the shortcut associated with a plugin is triggered (multiple commands by plugin is still achievable through register_command from the plugin API).
  • REVEN Axion will load any plugin in its autoload directories (user’s default autoload directory is $HOME/.config/tetrane/REVEN Axion-plugins/autoload).
  • To reload a plugin you can just type REVEN Axion.plugins().reload_plugin('my_plugin_name') in REVEN Axion’s python console instead of restarting the application.

Getting started with a first dummy plugin stub:

  1. Create a file my_percent.py in your autoload directory
      def REVEN Axion_callback():
          print "percent plugin called"
  1. Launch REVEN Axion, define a shortcut (using menu Actions > Edit shortcuts or F11 by default) for your brand new plugin.

Shortcuts

  1. Try to trigger your plugin by calling its shortcut. See resulting output in the Python console widget

console_output_trigger

Retrieving the selected instruction

Now that our plugin can be called, let’s retrieve the selected instruction from REVEN Axion’s API.

  from REVEN Axion_api import REVEN Axion

  def REVEN Axion_callback():
      run, seq, instr = REVEN Axion.selected_sequence()

      # run: current run name as a string
      # seq: index of selected sequence in current run
      # instr: index of selected instruction in selected sequence.
      msg = "Calling percent plugin on instruction at [%s@%d:%d]" % (run, seq, instr)

      # output to python console
      print msg
      # log message to status bar
      REVEN Axion.status_message(msg, 100000)
Tip:

Use REVEN Axion.status_message(msg, duration_ms = 5000) instead of print to have your logs printed in the status bar.

percent_console_output_selected

Once we find a matching instruction we will need to select it in REVEN Axion’s view, which can be done by using:

    REVEN Axion.select_sequence(run_name, sequence_identifier, instruction_index)

Using REVEN API

REVEN Axion’s client may not have all information required to resolve our matching instruction in memory. Hence we will connect to the REVEN project instance to query any missing information.

APIs summary

APIs summary

  • REVEN Axion is connected to a REVEN project instance through REVEN’s network interface.
  • Plugin can communicate with REVEN Axion through its plugin API.
  • Plugin can create its own connection to REVEN project instance to garther information not loaded by REVEN Axion as any standalone Python script.

Connecting to current REVEN project instance

We connect to the current REVEN project instance through REVEN’s Python API and create an execution point object from the selected instruction.


  import REVEN

  def REVEN Axion_callback():
      run, seq, instr = REVEN Axion.selected_sequence()

      # get current connection info
      host, port = REVEN Axion.connection_info()
      # connect to REVEN using its python API (used by REVEN scripts)
      client = REVEN.REVEN_connection(host.encode(), port)
      # create execution point common in REVEN scripts
      point = REVEN.execution_point(run.encode(), seq, instr)
Warning
  • Do not forget to import REVEN.
  • Why encode() ? The plugin API, which relies on PythonQt (the REVEN Axion object), uses utf-8 strings (thanks to Qt) while REVEN’s Python API is using vanilla python strings.

Now that we have our execution point, we can query more data from REVEN project instance. Let’s define a function get_matching_instruction(client, point) which will return the matching instruction’s execution point or None.

None is returned if no matching instruction exists or if the matching execution point is not present in current execution trace.

This gives us the following skeleton:


  from REVEN Axion_api import REVEN Axion
  import REVEN

  def get_matching_instruction(client, point):
      # to be implemented:
      # - retrieve context
      # - ss register heuristic
      # - esp register heursitic
      # - memory heuristic

  def REVEN Axion_callback():
      run, seq, instr = REVEN Axion.selected_sequence()
      host, port = REVEN Axion.connection_info()
      client = REVEN.REVEN_connection(host.encode(), port)
      point = REVEN.execution_point(run.encode(), seq, instr)

      result = get_matching_instruction(client, point)

      if result == None or not result.valid():
          print "No matching instruction recorded"
          return

      # select matching instruction
      REVEN Axion.select_sequence(result.run_name, result.sequence_identifier, result.instruction_index)
Trivial heuristic monitoring stack segment

This will handle sysenter/sysexit and int/iret.

If the stack segment register ss is modified by the selected instruction, we will consider the next instruction writing ss as the matching one.


  def get_matching_instruction(client, point):
      # create a range of size 1 to query context before and after selected instruction
      point_range = REVEN.execution_range(point.run_name, point.sequence_identifier, 1,
                                          point.instruction_index)
      # empty vector of logical address range to ignore memory
      context = client.run_get_running_context_between(point_range,
                                                       REVEN.vector_of_logical_address_range())

      # ss value before selected instruction
      ss_before = context.before.numeric_registers['ss'].value
      # ss value after selected instruction
      ss_after = context.after.numeric_registers['ss'].value

      if ss_before != ss_after:
          # ss is modified by instruction
          # search next write for ss and return corresponding execution point
          return client.run_search_next_register_use(point, forward=(ss_before > ss_after),
                                                     read=False, write=True, register_name="ss")

Test it in REVEN Axion:

int 0x80 selected Jumping from int 0x80

jumped from int 0x80 to matching instruction, iretd selected Landed on matching iretd

going back to first int 0x80 for it is matching iretd Going back to previous int 0x80

Monitoring the stack pointer

This will handle push/pop and call/ret.


  # retrieving esp value before and after selected instruction
  esp_before = context.before.numeric_registers['esp'].value
  esp_after = context.after.numeric_registers['esp'].value

  if esp_before != esp_after:
      if esp_before > esp_after:
          # push-like instruction
          stack = REVEN.logical_address(ss_after, esp_after)
          # search next read of pushed value
          return client.run_search_next_memory_use(point, forward=True,
                        read=True, write=False, address=stack)

      if esp_before < esp_after:
          # pop-like instruction
          stack = REVEN.logical_address(ss_before, esp_before)
          # search previous write of popped value
          return client.run_search_next_memory_use(point, forward=False,
                        read=False, write=True, address=stack)

Going further: memory based heuristic

  def pick_memory_access(client, point):
      "Return first non null logical address accessed by instruction at given execution point or None"
      null_logical = REVEN.logical_address(0, 0)
      result = None

      accesses = client.memory_get_history_instruction(point)
      for access in accesses:
          if access.logical != null_logical:
              if not (result and result.write and not access.write):
                  result = access
      return result

      def get_maching_instruction(client, point):
          # skipping context retrieval and previous heuristics
          # ...

          access = pick_memory_access(client, point)
          if access != None:
              if access.write:
                  return client.run_search_next_memory_use(point, forward=True,
                                read=True, write=False, address=access.logical)

              if access.read:
                  return client.run_search_next_memory_use(point, forward=False,
                                read=False, write=True, address=access.logical)

Pedantic instruction matching

With the current implementation, if a pushed value is read before the pop, the match would be the first reading instruction and not the actual pop. To get the true pop - if it exists - we could monitor esp value by channeling run_search_next_register_use calls to find a greater or equal value for esp. This way, we would even be able to find a pop disguised as an esp increment (add esp, 0x4).

Conclusion

In this article, we’ve seen how one can easily implement simple heuristics to add specific functionality to REVEN Axion, thanks to its Python API.

This widget did not require a graphical interface, but had it been the case we could have created our own custom widgets using PythonQt and connected them to REVEN Axion.

Next post: Unfolding obfuscated code (part 1)
Previous post: SWF file unpacking with REVEN